/**
 *  Copyright (c) Filipe Manana <fdmanana@gmail.com>
 *  All rights reserved.
 * 
 *  This file is part of jsdet.
 *
 *  jsdet is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  jsdet is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with jsdet.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jsdet.arguments;


import java.util.*;
import java.util.regex.*;

import jsdet.utils.ExpandDirectory;


/**
 * 
 * @author Filipe Manana
 *
 */
public class Arguments {

    public static final Pattern FILE_EXTENSION_PATTERN = Pattern.compile(".*? \\.js $",
            Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);
    public static final Pattern CSV_PATTERN = Pattern.compile("^ \\s* [\\w$\\.]+ (?: \\s* , \\s* [\\w$\\.]+ )* \\s* $",
            Pattern.COMMENTS);


    private String[] originalArgs = new String[0];
    private Set<String> files = new LinkedHashSet<String>();
    private Set<String> classes = new HashSet<String>();
    private Set<Option> options = new HashSet<Option>();
    private Map<Option, String> optionValues = new HashMap<Option, String>();


    public static Arguments parseArguments(String[] args) throws IllegalArgumentException {
        Arguments arguments = new Arguments();

        for ( int i = 0; i < args.length; ) {
            String arg = args[i];
            Option option = null;

            if ( arg.startsWith("-") ) {
                // a command line option

                option = Option.match(arg);

                if ( option != null ) {
                    if ( option.hasValue() ) {
                        String optionValue = null;

                        try {
                            optionValue = args[i + 1];
                        }
                        catch ( ArrayIndexOutOfBoundsException ex ) {
                            String msg = "Option value is missing for option `" + arg + "'";
                            throw new IllegalArgumentException(msg);
                        }

                        if ( option == Option.ONLY_CLASSES ) {
                            Set<String> classes = processClassList(arg, optionValue);
                            arguments.addClasses(classes);
                        }
                        else {
                            arguments.addOption(option, optionValue);
                        }
                    }
                    else {
                        arguments.addOption(option);
                    }
                }
                else {
                    if ( arg.equals("-") ) {
                        // not an option... instead it means: use standard
                        // input"
                        arguments.addFile(arg);
                    }
                    else {
                        throw new IllegalArgumentException("Options `" + arg
                                + "' is not available.");
                    }
                }
            }
            else {
                // it's a file or directory name

                Set<String> subdirFiles = null;

                try {
                    subdirFiles = ExpandDirectory.getFiles(arg, FILE_EXTENSION_PATTERN);
                }
                catch ( IllegalArgumentException ex ) {
                    throw new IllegalArgumentException("Argument `" + arg
                            + "' is neither a JavaScript file or a directory.");
                }

                arguments.addFiles(subdirFiles);
            }

            if ( option != null && option.hasValue() ) {
                i += 2;
            }
            else {
                i += 1;
            }

        } // end for args

        if ( arguments.hasOption(Option.SORT_CLASSES_ASC) && arguments.hasOption(Option.SORT_CLASSES_DESC) ) {
            String msg = "Options `" + Option.SORT_CLASSES_ASC.getLongName() + "' and `";
            msg += Option.SORT_CLASSES_DESC.getLongName() + "' can not be combined.";

            throw new IllegalArgumentException(msg);
        }

        if ( arguments.hasOption(Option.NAMES_ONLY) ) {
            if ( arguments.hasOption(Option.SHOW_INHERITED_MEMBERS) ) {
                String msg = "Use of option `" + Option.SHOW_INHERITED_MEMBERS.getLongName();
                msg += "' / `" + Option.SHOW_INHERITED_MEMBERS.getShortName();
                msg += "' is not compatible with option `" + Option.NAMES_ONLY.getLongName() + "'.";

                throw new IllegalArgumentException(msg);
            }
        }

        int outputOptionsCount = 0;

        if ( arguments.hasOption(Option.XML) ) {
            outputOptionsCount += 1;
        }
        if ( arguments.hasOption(Option.SIMPLE_HTML) ) {
            outputOptionsCount += 1;
        }
        if ( arguments.hasOption(Option.HTML_GUI) ) {
            outputOptionsCount += 1;
        }

        if ( outputOptionsCount > 1 ) {
            String msg = "Output format options can not be combined. Use only one of: `";
            msg += Option.SIMPLE_HTML.getLongName() + "', `" + Option.XML.getLongName() + "', `";
            msg += Option.HTML_GUI.getLongName() + "'.";

            throw new IllegalArgumentException(msg);
        }

        if ( arguments.getFiles().isEmpty() && !arguments.hasOption(Option.HELP) ) {
            throw new IllegalArgumentException("At least one JavaScript file or directory must be supplied.");
        }

        arguments.setOriginalArgs(args);

        return arguments;
    }


    public Arguments() {
    }


    public String[] getOriginalArgs() {
        return originalArgs;
    }


    public void setOriginalArgs(String[] originalArgs) {
        this.originalArgs = originalArgs;
    }


    public Set<String> getFiles() {
        return files;
    }


    public void addFile(String fileName) {
        files.add(fileName);
    }


    public void addFiles(Collection<String> fileNames) {
        files.addAll(fileNames);
    }


    public void setFiles(Set<String> files) {
        this.files = files;
    }


    public boolean haveClassesForFiltering() {
        return !classes.isEmpty();
    }


    public Set<String> getClasses() {
        return this.classes;
    }


    public void addClass(String className) {
        classes.add(className);
    }


    public void addClasses(Collection<String> classes) {
        this.classes.addAll(classes);
    }


    public void setClasses(Set<String> classes) {
        this.classes = classes;
    }


    public boolean hasOption(Option option) {
        return options.contains(option);
    }


    public Set<Option> getOptions() {
        return options;
    }


    public void addOption(Option option) {
        addOption(option, null);
    }


    public void addOption(Option option, String value) {
        options.add(option);

        if ( value != null ) {
            optionValues.put(option, value);
        }
    }


    public void setOptions(Set<Option> options) {
        this.options = options;
    }


    public boolean optionHasValue(Option option) {
        return optionValues.containsKey(option);
    }


    public String getOptionValue(Option option) {
        return optionValues.get(option);
    }


    public Map<Option, String> getOptionValues() {
        return optionValues;
    }


    public void setOptionValues(Map<Option, String> optionValues) {
        this.optionValues = optionValues;
    }


    private static Set<String> processClassList(String arg, String list) throws IllegalArgumentException {
        list = list.trim();
        Matcher m = CSV_PATTERN.matcher(list);

        if ( !m.matches() ) {
            String msg = "The value for option `" + arg + "' must be a comma separated list of JavaScript class names.";
            throw new IllegalArgumentException(msg);
        }

        String[] values = list.split("\\s*,\\s*");

        return new HashSet<String>(Arrays.asList(values));
    }
}
